#include "Character.h"
#include "Item.h"
#include "Game.h"
#include "Potion.h"
#include "Resource.h"
#include "Obstacle.h"
#include "Equipment.h"
#include "Util.h"
#include "Tile.h"
#include "ItemStack.h"
#include "Shield.h"
#include "Armor.h"
#include "Trap.h"
#include "GameState.h"
#include "MeleeWeapon.h"
#include "RangedWeapon.h"
#include "StringLib.h"
#include "Level.h"
#include "Door.h"
#include "Load.h"

int Character::levelThresholds[MAX_LEVEL] = {
	0, 50, 100, 150, 200, 300, 400, 500, 600, 700,
	800, 900, 1000, 1150, 1300, 1450, 1600, 1750, 1900, 2050,
	2200, 2400, 2600, 2800, 3000, 3200, 3400, 3600, 3800, 4000,
	4200, 4500, 4800, 5100, 5400, 5700, 6000, 6300, 6600, 6900,
	7200, 7600, 8000, 8400, 8800, 9500, 10200, 11000, 12000, 13000,
};

Character::Character(GenericType type, GenderType gender, bool hostile) : GameObject(type) {
	m_inventory = new std::list<Item*>();
	m_humanoid = true;
	m_hostile = hostile;
	m_tile = NULL;
	m_currentWeight = 0;
	m_gender = gender;
	m_turnCounter = DEFAULT_TURN_LENGTH + x_plus_or_minus(0, DEFAULT_TURN_LENGTH/10);
	m_equipment = new Equipment();
	m_regenRate = DEFAULT_TURN_LENGTH * 4;
	m_regenCounter = m_regenRate;
	m_experience = 0;
	m_armorBonus = 0;
	m_dodgeBonus = 0;
	m_block = 0;
	for(int i = 0; i < NUM_SKILLS; i++)
		m_skills[Skill(i)] = 0.0f;
}

Character::Character(GenericType type, std::ifstream& file) : GameObject(type) {
	int* tokens = Util::intSplit(file);
	m_level = tokens[0];
	m_currentHealth = tokens[1];
	m_maxHealth = tokens[2];
	m_turnCounter = tokens[3];
	m_regenRate = tokens[4];
	m_regenCounter = tokens[5];
	m_gender = GenderType(tokens[6]);
	m_hostile = tokens[7] == 1;
	m_experience = tokens[8];
	for(int i = 0; i < NUM_STATS; i++)
		m_stats[i] = tokens[9+i];
	for(int i = 0; i < NUM_STATUS_EFFECTS; i++) {
		int status = tokens[9+NUM_STATS+i];
		if(status > 0)
			m_statusEffects[StatusEffect(i)] = tokens[9+NUM_STATS+i];
	}
	delete[] tokens;
	float* floatTokens = Util::floatSplit(file);
	for(int i = 0; i < NUM_SKILLS; i++)
		m_skills[Skill(i)] = floatTokens[i];
	delete[] floatTokens;
	m_inventory = new std::list<Item*>();
	m_tile = NULL;
	m_currentWeight = 0;
	m_equipment = new Equipment();
	m_armorBonus = 0;
	m_humanoid = true;
	m_dodgeBonus = 0;
	m_block = 0;
	tokens = Util::intSplit(file);
	for(int i = 0; i < tokens[0]; i++) {
		Item* item = Load::loadItem(file);
		forceItemInInventory(item);
		if(item->isEquipped())
			Character::equip(item->getEquipLocation(), item, false);
	}
	delete[] tokens;
}

Character::~Character() {
	delete m_equipment;
	for(std::list<Item*>::iterator iter = m_inventory->begin(); iter != m_inventory->end(); iter++) {
		delete *iter;
	}
	delete m_inventory;
	m_statusEffects.clear();
}

void Character::save(std::ofstream& file) const {
	file << m_level << " " << m_currentHealth << " " << m_maxHealth << " ";
	file << m_turnCounter << " " << m_regenRate << " " << m_regenCounter << " ";
	file << m_gender << " " << (m_hostile ? 1 : 0) << " " << m_experience;
	for(int i = 0; i < NUM_STATS; i++)
		file << " " << m_stats[i];
	for(int i = 0; i < NUM_STATUS_EFFECTS; i++) {
		if(hasStatusEffect(StatusEffect(i)))
			file << " " << m_statusEffects.at(StatusEffect(i));
		else
			file << " " << 0;
	}
	file << "\n";
	for(int i = 0; i < NUM_SKILLS; i++)
		file << m_skills.at(Skill(i)) << ((i == NUM_SKILLS-1) ? "" : " ");
	file << "\n";
	file << m_inventory->size() << "\n";
	for(std::list<Item*>::const_iterator iter = m_inventory->begin(); iter != m_inventory->end(); iter++)
		(*iter)->save(file);
}

void Character::setTile(Tile* tile) {
	if(m_tile)
		m_tile->removeCharacter();
	m_tile = tile;
}

void Character::insertItem(std::list<Item*>* items, Item* item) {
	std::list<Item*>::reverse_iterator iter = items->rbegin();
	for(; iter != items->rend(); iter++) {
		if(ItemStack::filterMap[GenericType(item->getType(TH_GENERIC_TYPE))] < ItemStack::filterMap[GenericType((*iter)->getType(TH_GENERIC_TYPE))]) {
			continue;
		} else {
			items->insert(iter.base(), item);
			return;
		}
	}
	if(iter == items->rend())
		items->push_front(item);
	else
		items->push_back(item);
}

bool Character::addItemToInventory(Item* item) {
	if(m_currentWeight + item->getWeight() > m_stats[STR]*1000) return false;
	forceItemInInventory(item);
	return true;
}

void Character::forceItemInInventory(Item* item) {
	Item* existingItem = getItem(item);
	if(existingItem != NULL && item->isStackable()) {
		existingItem->addToStack(item);
		if(item->getParentList() != NULL)
			item->getParentList()->remove(item);
		g_gameState->markForDeletion(item);
	} else
		insertItem(m_inventory, item);
	m_currentWeight += item->getWeight();
	if(item->getParentList() != NULL)
		item->getParentList()->remove(item);
	item->setParentList(m_inventory);
}

void Character::forceRemoveItemFromInventory(Item* item) {
	m_inventory->remove(item);
	m_currentWeight -= item->getWeight();
}

bool Character::pickUp(Item* item) {
	if(addItemToInventory(item)) {
		modifyTurnCounter(DEFAULT_TURN_LENGTH/2);
		return true;
	}
	return false;
}

bool Character::equip(EquipmentLocation location, Item* item, bool takeTurn) {
	if(m_equipment->equip(location, item)) {
		if((location == EL_LEFT_HAND || location == EL_RIGHT_HAND)) {
			if(item->getType(TH_GENERIC_TYPE) == GT_IT_SHIELD)
				m_block += ((Shield*)item)->getBlock();
		} else {
			m_dodgeBonus += ((Armor*)item)->getDodge();
			m_armorBonus += ((Armor*)item)->getArmor();
		}
		if(takeTurn)
			modifyTurnCounter(DEFAULT_TURN_LENGTH/2);
		return true;
	}
	return false;
}

bool Character::unequip(EquipmentLocation location) {
	Item* item = m_equipment->get(location);
	if(m_equipment->unequip(location)) {
		if(location == EL_LEFT_HAND || location == EL_RIGHT_HAND) {
			if(item->getType(TH_GENERIC_TYPE) == GT_IT_SHIELD)
				m_block -= ((Shield*)item)->getBlock();
		} else {
			m_dodgeBonus -= ((Armor*)item)->getDodge();
			m_armorBonus -= ((Armor*)item)->getArmor();
		}
		modifyTurnCounter(DEFAULT_TURN_LENGTH/2);
		return true;
	}
	return false;
}

void Character::logDetails(TextLog& log) const {
	GameObject::logDetails(log);
}

std::string Character::getColoredName() const {
	return "\r5" + getName() + "\r5";
}

std::string Character::getColoredBasicName() const {
	return "\r5" + getBasicName() + "\r5";
}

HealthStatus Character::getHealthStatus() const {
	float healthPercent = float(m_currentHealth) / float(m_maxHealth) * 100.0f;
	if(healthPercent >= 100)
		return HS_NOT_WOUNDED;
	else if(healthPercent >= 75)
		return HS_SLIGHTLY_WOUNDED;
	else if(healthPercent >= 50)
		return HS_MODERATELY_WOUNDED;
	else if(healthPercent >= 25)
		return HS_HEAVILY_WOUNDED;
	else if(healthPercent > 0)
		return HS_NEAR_DEATH;
	else
		return HS_DEAD;
}

bool Character::move(int x, int y) {
	int newX = int(m_tile->getX())+x;
	int newY = int(m_tile->getY())+y;
	if(m_tile->getLevel()->getTile(newX, newY)->getCharacter() != NULL && (x != 0 || y != 0)) {
		if((m_tile->getLevel()->getTile(newX, newY)->getCharacter()->getType(TH_GENERIC_TYPE) == GT_CH_NPC && m_hostile != m_tile->getLevel()->getTile(newX, newY)->getCharacter()->isHostile())
				|| m_tile->getLevel()->getTile(newX, newY)->getCharacter()->getType(TH_GENERIC_TYPE) == GT_CH_PLAYER) {
			doMeleeAttack(m_tile->getLevel()->getTile(newX, newY)->getCharacter());
			return true;
		}
	} else if(m_tile->getLevel()->getTile(newX, newY)->getObstacle() != NULL && m_tile->getLevel()->getTile(newX, newY)->getObstacle()->getType(TH_GENERIC_TYPE) == GT_OB_DOOR
			&& !((Door*)m_tile->getLevel()->getTile(newX, newY)->getObstacle())->isOpen() && m_humanoid) {
		openDoor((Door*)(m_tile->getLevel()->getTile(newX, newY)->getObstacle()));
		return true;
	} else if(!m_tile->getLevel()->getTile(newX, newY)->isOccupied() || (x == 0 && y == 0)) {
		moveToNewTile(newX, newY);
		modifyTurnCounter(DEFAULT_TURN_LENGTH);
		return true;
	}
	return false;
}

void Character::moveToNewTile(int x, int y) {
	m_tile->getLevel()->getTile(x, y)->addCharacter(this);
	if(m_tile->getObstacle() && m_tile->getObstacle()->getType(TH_GENERIC_TYPE) == GT_OB_TRAP) {
		Trap* trap = ((Trap*)m_tile->getObstacle());
		trap->trigger(this);
		if(trap->getNumTriggers() <= 0) {
			trap->getTile()->removeObstacle();
			g_gameState->markForDeletion(trap);
		}
	}
}

bool Character::inventoryContains(TypeHierarchy typeCategory, int type, int num, bool same) const {
	int count = 0, counter = 0;
	for(std::list<Item*>::const_iterator iter = m_inventory->begin(); size_t(counter) < m_inventory->size() && count < num; iter++, counter++) {
		Item* item = *iter;
		if(item->getType(typeCategory) == type) {
			if(same) {
				count = item->getNumStacked();
				std::list<Item*>::const_iterator iter2 = m_inventory->begin();
				std::advance(iter2, counter+1);
				for(; iter2 != m_inventory->end() && count != num; iter2++) {
					if(item->typeEqual(*(*iter2)))
						count += (*iter2)->getNumStacked();
				}
			} else {
				count += item->getNumStacked();
			}
		}
	}
	return count >= num;
}

Item* Character::findItem(TypeHierarchy typeCategory, int type) const {
	for(std::list<Item*>::const_iterator iter = m_inventory->begin(); iter != m_inventory->end(); iter++) {
		if((*iter)->getType(typeCategory) == type)
			return *iter;
	}
	return NULL;
}

Item* Character::getItem(Item* item) const {
	for(std::list<Item*>::const_iterator iter = m_inventory->begin(); iter != m_inventory->end(); iter++) {
		if(item->typeEqual(**iter) && item->isStatusIdentified() == (*iter)->isStatusIdentified() && item->getStatus() == (*iter)->getStatus() && item->specialEqual(*iter))
			return *iter;
	}
	return NULL;
}

bool Character::hasKey(LockType lockType) const {
	if(inventoryContains(TH_MISC_TYPE, MT_KEY_SKELETON)) return true;
	switch(lockType) {
	case LT_NONE:
		return true;
	case LT_ROUND:
		return inventoryContains(TH_MISC_TYPE, MT_KEY_ROUND);
	case LT_SQUARE:
		return inventoryContains(TH_MISC_TYPE, MT_KEY_SQUARE);
	}
	return false;
}

void Character::doMeleeAttack(Character* c) {
	if(m_humanoid) {
		Item* leftHand = m_equipment->get(EL_LEFT_HAND), *rightHand = m_equipment->get(EL_RIGHT_HAND);
		MeleeType leftType = MT_FIST, rightType = MT_FIST;

		if(leftHand != NULL) {
			if(Weapon::isWeapon(leftHand))
				leftType = MT_WEAPON;
			else if(leftHand->getType(TH_GENERIC_TYPE) == GT_IT_SHIELD)
				leftType = MT_SHIELD;
			else
				leftType = MT_ITEM;
		}

		if(rightHand != NULL) {
			if(Weapon::isWeapon(rightHand))
				rightType = MT_WEAPON;
			else if(rightHand->getType(TH_GENERIC_TYPE) == GT_IT_SHIELD)
				rightType = MT_SHIELD;
			else
				rightType = MT_ITEM;
		}

		if(leftType == MT_SHIELD)
			callMeleeAttack(c, rightType, rightHand);
		else if(rightType == MT_SHIELD)
			callMeleeAttack(c, leftType, leftHand);
		else if(leftType == MT_FIST) {
			if(rightType == MT_FIST) {
				callMeleeAttack(c, leftType);
				if(c->getHealthStatus() != HS_DEAD)
					callMeleeAttack(c, rightType);
			} else {
				callMeleeAttack(c, rightType, rightHand, true);
			}
		} else if(rightType == MT_FIST) {
			callMeleeAttack(c, leftType, leftHand, true);
		} else {
			callMeleeAttack(c, leftType, leftHand);
			if(c->getHealthStatus() != HS_DEAD)
				callMeleeAttack(c, rightType, rightHand);
		}
	} else {
		callMeleeAttack(c, MT_OTHER);
	}
}

void Character::callMeleeAttack(Character* c, MeleeType type, Item* item, bool otherHandEmpty) {
	switch(type) {
	case MT_FIST:
		meleeAttack(c, rand_plus_one(4), 0, 50);
		return;
	case MT_SHIELD:
		return;
	case MT_ITEM:
		meleeAttack(c, item->getRandomDamage());
		return;
	case MT_WEAPON:
		if(item->getType(TH_GENERIC_TYPE) == GT_IT_MELEE_WEAPON)
			meleeAttack(c, item->getRandomDamage(), ((Weapon*)item)->getAccuracy(), ((Weapon*)item)->getSpeed(), otherHandEmpty, ((Weapon*)item)->getGripType(), ((MeleeWeapon*)item)->getPrefix(), ((MeleeWeapon*)item)->getSuffix());
		else
			meleeAttack(c, item->getRandomDamage(), ((Weapon*)item)->getAccuracy(), ((Weapon*)item)->getSpeed(), otherHandEmpty, ((Weapon*)item)->getGripType());
		return;
	case MT_KICK:
		meleeAttack(c, rand(m_stats[STR]), (m_stats[NIM]/2)-3, 100);
	}
}

void Character::meleeAttack(Character* c, int damage, int accuracy, int speed, bool otherHandEmpty, GripType gripType, MeleeWeaponPrefix prefix, MeleeWeaponSuffix suffix) {
	if(c == NULL) {
		logCombat(c, true);
	} else if(rand(std::max(1,m_stats[NIM]+accuracy)) > rand(std::max(1,c->getStat(NIM)-3+c->getDodgeBonus()))) {
		float modifier = 1.0f;
		bool crit, flame, envy, lust;
		crit = one_in(100-m_stats[NIM]-getSkillLevel(SKI_EXPLOIT)*2);
		flame = prefix == MWP_FLAMING;
		envy = suffix == MWS_OF_ENVY && c->getLevel() > m_level;
		lust = suffix == MWS_OF_LUST && isOppositeGender(c->getGenderType());

		if(crit) {
			modifier += 1.0f + float(getSkillLevel(SKI_FINESSE))/2.0f;
			if(one_in(100-m_stats[LOR]))
				trainSkill(SKI_EXPLOIT, 0.1f*float(rand(2)));
			if(one_in(100-m_stats[LOR]))
				trainSkill(SKI_FINESSE, 0.1f*float(rand(2)));
		}

		if(flame)
			modifier += 0.5f;

		if(envy)
			modifier += 0.5f;
		else if(lust)
			modifier += 0.5f;
		if(gripType == GT_ONE_FIVE && otherHandEmpty)
			modifier += 0.1f;
		damage = std::max(0, damage + m_stats[STR]/2 - (c->getStat(STR)/2+c->getArmorBonus()-1));
		damage = int(damage*modifier);

		if(x_in_y(c->getBlock(), rand(100))) {
			logCombat(c, false, true, true, damage, crit, flame, envy, lust);
		} else {
			logCombat(c, false, true, false, damage, crit, flame, envy, lust);
			if(c->dealDamage(damage)) {
				giveExperience(int(std::max(1, c->getLevel()-m_level)) * 10 + 5);
			}
			if(prefix == MWP_POISONOUS)
				c->addStatusEffect(SE_POISON, 50);
		}
	} else {
		logCombat(c, false, false);
	}
	modifyTurnCounter(int(DEFAULT_TURN_LENGTH * (float(speed)/100.0f)));
}

bool Character::isOppositeGender(GenderType gender) const {
	if((m_gender == GT_MALE && gender == GT_FEMALE) || (m_gender == GT_FEMALE && gender == GT_MALE))
		return true;
	return false;
}

bool Character::dealDamage(int damage) {
	m_currentHealth -= damage;
	if(m_currentHealth <= 0) return true;
	return false;
}

void Character::giveExperience(int exp) {
	m_experience += exp;
}

bool Character::poison(int damage) {
	return dealDamage(damage);
}

std::string Character::getName() const {
	if(m_gender == GT_MALE || m_gender == GT_FEMALE)
		return g_stringLib->getMiscString(MST_GENDER, m_gender) + " " + GameObject::getName();
	return GameObject::getName();
}

void Character::heal(int amount) {
	m_currentHealth = std::min(m_maxHealth, amount);
}

bool Character::quaff(Potion* potion) {
	bool quaffed = false;
	switch(potion->getType(TH_POTION_TYPE)) {
	case PT_HEALING: {
		int health = m_currentHealth + potion->getPower()*10;
		switch(potion->getStatus()) {
		case CURSED:
			health = int(health * 0.5);
			break;
		case BLESSED:
			health = int(health * 1.5);
			break;
		}
		heal(health);
		removeStatusEffect(SE_POISON);
		quaffed = true;
		break;
	}
	case PT_POISON: {
		int power = potion->getPower()*50;
		if(potion->getStatus() == CURSED)
			power = int(power * 1.25f);
		else if(potion->getStatus() == BLESSED)
			power = int(power * 0.80f);
		addStatusEffect(SE_POISON, power);
		quaffed = true;
		break;
	}
	case PT_INVIS: {
		int turns = potion->getPower()*3;
		if(potion->getStatus() == CURSED)
			turns = int(turns * 0.80f);
		else if(potion->getStatus() == BLESSED)
			turns = int(turns * 1.25f);
		addStatusEffect(SE_INVIS, turns);
		quaffed = true;
		break;
	}
	}
	if(quaffed) {
		modifyTurnCounter(DEFAULT_TURN_LENGTH / 3);
		potion->reduceStackCount(1, true, m_inventory);
	}
	return quaffed;
}

void Character::changeStat(Stat stat, int mod) {
	m_stats[stat] += mod;
}

void Character::modifyTurnCounter(int mod) {
	m_turnCounter += mod;
	if(mod == -DEFAULT_TURN_LENGTH) {
		if(m_currentHealth != m_maxHealth) {
			m_regenCounter += mod;
			while(m_regenCounter < 0 && m_currentHealth != m_maxHealth) {
				m_currentHealth++;
				m_regenCounter += m_regenRate;
			}
		}
		for(std::map<StatusEffect, int>::iterator iter = m_statusEffects.begin(); iter != m_statusEffects.end(); iter++) {
			switch(iter->first) {
			default:
				m_statusEffects[iter->first] -= 1;
				break;
			case SE_POISON:
				m_statusEffects[SE_POISON] -= std::max(1, m_stats[STR]/2);
				if(x_in_y(3, m_stats[STR]/2)) {
					poison(rand_plus_one(m_statusEffects[SE_POISON]/100+1));
				}
				break;
			}
		}
		for(StatusEffect i = SE_POISON; i < NUM_STATUS_EFFECTS; i = StatusEffect(int(i+1))) {
			if(hasStatusEffect(i) <= 0)
				removeStatusEffect(i);
		}
	}
}

bool Character::addStatusEffect(StatusEffect effect, int power) {
	if(m_statusEffects.count(effect) > 0) {
		switch(effect) {
		default:
			m_statusEffects[effect] = std::max(power, m_statusEffects[effect]);
			break;
		case SE_POISON:
			m_statusEffects[effect] += power;
			break;
		}
		return true;
	} else {
		m_statusEffects[effect] = power;
		return false;
	}
}

int Character::hasStatusEffect(StatusEffect effect) const {
	if(m_statusEffects.count(effect) > 0)
		return m_statusEffects.at(effect);
	return 0;
}

bool Character::removeStatusEffect(StatusEffect effect) {
	if(m_statusEffects.count(effect) > 0) {
		m_statusEffects.erase(effect);
		return true;
	}
	return false;
}

void Character::trainSkill(Skill skill, float amount) {
	m_skills[skill] = std::min(10.0f, m_skills[skill] + amount);
}

void Character::reload() {
	Item* leftHand = m_equipment->get(EL_LEFT_HAND);
	if(leftHand != NULL && leftHand->getType(TH_GENERIC_TYPE) == GT_IT_RANGED_WEAPON) {
		if(inventoryContains(TH_AMMO_TYPE, ((RangedWeapon*)leftHand)->getAmmoType())) {
			if(((RangedWeapon*)leftHand)->reload((Ammo*)findItem(TH_AMMO_TYPE, ((RangedWeapon*)leftHand)->getAmmoType()))) {
				modifyTurnCounter(DEFAULT_TURN_LENGTH);
				logReload((RangedWeapon*)leftHand);
			}
		} else {
			logReload((RangedWeapon*)leftHand, true);
		}
	}

	Item* rightHand = m_equipment->get(EL_RIGHT_HAND);
	if(rightHand != NULL && leftHand->getType(TH_GENERIC_TYPE) == GT_IT_RANGED_WEAPON) {
		if(inventoryContains(TH_AMMO_TYPE, ((RangedWeapon*)rightHand)->getAmmoType())) {
			if(((RangedWeapon*)rightHand)->reload((Ammo*)findItem(TH_AMMO_TYPE, ((RangedWeapon*)rightHand)->getAmmoType()))) {
				modifyTurnCounter(DEFAULT_TURN_LENGTH);
				logReload((RangedWeapon*)rightHand);
			}
		} else {
			logReload((RangedWeapon*)rightHand, true);
		}
	}
}
